package cn.code.core;


import cn.code.demo.TestIOC;
import cn.code.ex.NoSuchBeanException;
import cn.code.vo.BeanDefinition;

import java.io.File;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationClassPathContext {
    public static void main(String[] args) throws Exception {
        AnnotationClassPathContext annotationClassPathContext =
                new AnnotationClassPathContext("cn.code.demo");

        TestIOC testIOC = annotationClassPathContext.getBean("testIOC", TestIOC.class);
        testIOC.say();

    }

    private String basePackageName;
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    /**
     * 构造-扫包，默认是根包
     */
    public AnnotationClassPathContext() throws Exception {
        // 如果没有设置包名，默认使用本类所在的包为根包
        new AnnotationClassPathContext(null);
    }

    /**
     * 构造-扫包，默认是根包
     *
     * @param packageName
     */
    public AnnotationClassPathContext(String packageName) throws Exception {
        // 扫描根包  保存bean定义信息
        scanComponents(packageName);
        // 保存Bean实例
        registrySingletonObject();
    }

    // 扫描根包  保存bean定义信息
    private void scanComponents(String packageName) throws Exception {

        // 如果没有设置包名，默认使用本类所在的包为根包
        if (packageName == null || packageName.isEmpty()) {
            packageName = AnnotationClassPathContext.class.getPackage().getName();
        }
        this.basePackageName = packageName;
        // cn.code   - >  cn/code
        String path = packageName.replace(".", "/");
        // 加载根包下的类
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL resource = classLoader.getResource(path);
        File basePackage = new File(resource.getPath());
        // 遍历根包的所有类文件 保存类定义的信息到beanDefinitionMap中
        for (File file : basePackage.listFiles()) {
            String name = file.getName();
            // 比如TestIOC.class -> TestIOC
            name = name.substring(0, name.length() - 6);
            // 比如：TestIOC ->  testIOC
            String id = name.substring(0, 1).toLowerCase() + name.substring(1);
            String className = basePackageName + "." + name;

            //Class<?> aClass = classLoader.loadClass(className);

            // 保存beanDefinition信息
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setPackageClassName(className);
            beanDefinitionMap.put(id, beanDefinition);
        }
        System.out.println(beanDefinitionMap);

    }

    // 保存Bean实例
    private void registrySingletonObject() throws Exception {
        if (!beanDefinitionMap.isEmpty()) {
            Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
            for (Map.Entry<String, BeanDefinition> entry : entries) {
                String id = entry.getKey();
                BeanDefinition beanDefinition = entry.getValue();
                // 准备实例对象
                Class<?> aClass = Class.forName(beanDefinition.getPackageClassName());
                Object o = aClass.newInstance();

                // 保持单例
                synchronized (this) {
                    Object o1 = singletonObjects.get(id);
                    if (o1 != null) {
                        throw new IllegalStateException("不能注册这个" + id + "实例，这个" + id + "名称实例已经存在");
                    } else {
                        singletonObjects.put(id, o);
                    }
                }
            }
        }
    }

    // 提供get方法获取Bean的方法
    public Object getBean(String beanName) throws Exception {
        // 保持单例
        Object o = singletonObjects.get(beanName);
        synchronized (this) {
            if (o == null) {
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if (beanDefinition == null) {
                    throw new NoSuchBeanException("没有找到该bean信息");
                }
                Class<?> aClass = Class.forName(beanDefinition.getPackageClassName());
                o = aClass.newInstance();
                singletonObjects.put(beanName, o);
            }
        }
        return o;
    }

    public <T> T getBean(String beanName, Class<T> cls) throws Exception {
        Object bean = getBean(beanName);
        return (T) bean;
    }

}
